﻿using System;
using System.Collections.Generic;
using SpiceNet.Diagnostics;
using SpiceNet.Circuits;
using System.Numerics;
using SpiceNet.Models;

namespace SpiceNet.Parameters
{
    /// <summary>
    /// A class that contains parameters
    /// </summary>
    public abstract class Parameterized
    {
        /// <summary>
        /// Gets the name of the instance
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// A class that represents an instance parameter
        /// </summary>
        public class IP
        {
            public IF Flags { get; }
            public T Type { get; }
            public string Description { get; }
            public int Id { get; }

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="flags"></param>
            /// <param name="value"></param>
            /// <param name="type"></param>
            /// <param name="description"></param>
            public IP(IF flags, int id, T type, string description)
            {
                Flags = flags;
                Id = id;
                Type = type;
                Description = description;
            }
        }

        /// <summary>
        /// Each instance give a dictionary of parameters
        /// </summary>
        public abstract Dictionary<string, IP> ParamTable { get; }

        /// <summary>
        /// An enumeration of parameter flags
        /// </summary>
        public enum IF
        {
            REQUIRED = 0x4000,
            SET = 0x2000,
            ASK = 0x1000,
            REDUNDANT = 0x0010000,
            PRINCIPAL = 0x0020000,
            AC = 0x0040000,
            AC_ONLY = 0x0080000,
            NOISE = 0x0100000,
            NONSENSE = 0x0200000,

            SETQUERY = 0x0400000,
            ORQUERY = 0x0800000,
            CHKQUERY = 0x1000000,

            UNINTERESTING = 0x2000000, // For "show" command: do not print value in a table by default
            IOP = SET | ASK,
            IOPU = SET | ASK | UNINTERESTING,
            IOPP = SET | ASK | PRINCIPAL,
            IOPA = SET | ASK | AC,
            IOPAU = SET | ASK | AC | UNINTERESTING,
            IOPAP = SET | ASK | AC | PRINCIPAL,
            IOPAA = SET | ASK | AC_ONLY,
            IOPAAU = SET | ASK | AC_ONLY | UNINTERESTING,
            IOPPA = SET | ASK | AC_ONLY | PRINCIPAL,
            IOPN = SET | ASK | NOISE,
            IOPR = SET | ASK | REDUNDANT,
            IOPX = SET | ASK | NONSENSE,
            IOPXU = SET | ASK | NONSENSE | UNINTERESTING,
            IOPQ = SET | ASK | SETQUERY,
            IOPQU = SET | ASK | SETQUERY | UNINTERESTING,
            IOPZ = SET | ASK | CHKQUERY,
            IOPZU = SET | ASK | CHKQUERY | UNINTERESTING,
            IOPQO = SET | ASK | ORQUERY,

            IP = SET,
            OP = ASK,
            OPU = ASK | UNINTERESTING,
            OPR = ASK | REDUNDANT,
            P = 0x00
        }

        /// <summary>
        /// An enumeration of parameter types
        /// </summary>
        public enum T
        {
            FLAG = 0x1,
            INTEGER = 0x2,
            REAL = 0x4,
            COMPLEX = 0x8,
            NODE = 0x10,
            STRING = 0x20,
            INSTANCE = 0x40,
            PARSETREE = 0x80,
            VECTOR = 0x8000,
            WAVEFORM = 0x0100,

            FLAGVEC = FLAG | VECTOR,
            INTVEC = INTEGER | VECTOR,
            REALVEC = REAL | VECTOR,
            CPLXVEC = COMPLEX | VECTOR,
            NODEVEC = NODE | VECTOR,
            STRINGVEC = STRING | VECTOR,
            INSTVEC = INSTANCE | VECTOR
        }

        /// <summary>
        /// A delegate for input-only parameters
        /// </summary>
        /// <param name="value"></param>
        protected delegate void InputParam(object value, Circuit ckt);

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        public Parameterized(string name)
        {
            Name = name;
        }

        /// <summary>
        /// Set a parameter
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="id"></param>
        /// <param name="value"></param>
        protected virtual void Param(int id, object value, Circuit ckt = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Ask a parameter
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="id"></param>
        protected virtual object Ask(int id, Circuit ckt = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Gets or sets parameters from this instance
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public virtual object this[string key, Circuit ckt = null]
        {
            get
            {

                if (ParamTable == null || !ParamTable.ContainsKey(key))
                {
                    CircuitWarning.Warning(this, string.Format("{0}: Unrecognized parameter {1}", Name, key));
                    return null;
                }

                // Extract the value
                IP ip = ParamTable[key];
                if (ip.Flags.HasFlag(IF.ASK))
                    return this.Ask(ip.Id, ckt);
                else
                    CircuitWarning.Warning(this, string.Format("{0}: Cannot set parameter {1}", Name, key));
                return null;
            }
            set
            {
                if (ParamTable == null || !ParamTable.ContainsKey(key))
                {
                    CircuitWarning.Warning(this, string.Format("{0}: Unrecognized parameter {1}", Name, key));
                    return;
                }

                // Set the value
                IP ip = ParamTable[key];
                if (ip.Flags.HasFlag(IF.SET))
                {
                    // check the type of the value
                    object o = null;
                    switch (ip.Type)
                    {
                        case T.FLAG: o = Convert.ToBoolean(value); break;
                        case T.FLAGVEC: o = (bool[])value; break;
                        case T.INTEGER: o = Convert.ToInt32(value); break;
                        case T.INTVEC: o = (int[])value; break;
                        case T.REAL: o = Convert.ToDouble(value); break;
                        case T.REALVEC: o = (double[])value; break;
                        case T.STRING: o = Convert.ToString(value); break;
                        case T.STRINGVEC: o = (string[])value; break;
                        case T.COMPLEX: o = (Complex)value; break;
                        case T.CPLXVEC: o = (Complex[])value; break;
                        case T.WAVEFORM: o = (Waveform)value; break;
                        case T.INSTANCE: o = (Instance)value; break;
                        case T.INSTVEC: o = (Instance[])value; break;
                        case T.NODE: o = (CircuitNode)value; break;
                        case T.NODEVEC: o = (CircuitNode[])value; break;
                        default:
                            throw new BadParameterException(key);
                    }
                    if (o == null)
                        throw new BadParameterException(key);

                    Param(ip.Id, o, ckt);
                }
                else
                    CircuitWarning.Warning(this, string.Format("{0}: Cannot ask parameter {1}", Name, key));
            }
        }
    }
}
